﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using IRCServer.Entities;
using IRCServer.Utilities;
using IRCServer.Entities.Commands;
using IRCServer.Backend;
using IRC.Utilities;
using IRC.Utilities.Entities;

namespace IRCServer
{
    class IRCServer : IServer
    {
        private int _portNumber;
        private Socket server;

        public IRCServer(int portNumber)
        {
            _portNumber = portNumber;
        }
        #region IServer Members

        public void Start()
        {
            server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Any, _portNumber);
            server.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.ExclusiveAddressUse, false);
            Logger.Instance.Info(string.Format("Starting server with port {0}", _portNumber));
            server.Bind(iep);
            server.Listen(5);
            this.WaitForConnections();
            Logger.Instance.Info("Server attempting to communicate with neighbors.");
            Logger.Instance.Info("Server up and waiting for connections...");

            while (true)
            {
                System.Threading.Thread.Sleep(10000);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void WaitForConnections()
        {
            server.BeginAccept(new AsyncCallback(AcceptConnection), server);
        }

        public void AcceptConnection(IAsyncResult result)
        {
            Socket client = server.EndAccept(result);
            Logger.Instance.Info(string.Format("Accepted connection from {0}", client.RemoteEndPoint.ToString()));
            Session session = new Session()
            {
                Socket = client,
                ClientID = Backend.ServerBackend.Instance.ClientSessions.Count
            };
            Backend.ServerBackend.Instance.ClientSessions.Add(session);
            this.WaitForConnections();
            client.BeginReceive(session.Buffer, 0, session.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCommand), session);
            Logger.Instance.Info(string.Format("Ready to receive messages from Client {0}({1})", Backend.ServerBackend.Instance.ClientSessions.Count - 1, client.RemoteEndPoint.ToString()));
        }

        public void ReceiveCommand(IAsyncResult result)
        {
            Session session = (Session)result.AsyncState;

            int bytesReceived = 0;
            try
            {
                bytesReceived = session.Socket.EndReceive(result);
            }
            catch
            {
                Logger.Instance.Info("Error on EndReceive");
            }

            if (bytesReceived == 0)
            {
                // Zero bytes received means the client terminated the connection
                Logger.Instance.Info(string.Format("Closing connection with {0}", ClientDisplayString(session)));
                if (session.ConnectionState == ConnectionState.Registered)
                {
                    CommandFactory.GetCommandFromMessage("QUIT :Connection terminated", session).ExecuteCommand(session);
                }
                session.Socket.Close();
                return;
            }

            string message = System.Text.Encoding.ASCII.GetString(session.Buffer, 0, bytesReceived);
            Logger.Instance.Info(string.Format("Received '{0}' from {1}", message, ClientDisplayString(session)));
            string response = String.Empty;
            IRCCommandBase command;
            try
            {
                command = CommandFactory.GetCommandFromMessage(message, session);
                response = command.ExecuteCommand(session);
            }
            catch
            {
                response = Errors.GetErrorResponse(ErrorCode.ERR_UNKNOWNCOMMAND, String.Empty);
            }

            if (session.ConnectionState != ConnectionState.Destroyed) //Quit command has no replies. 
            {
                Logger.Instance.Info(string.Format("Responding with '{0}' to {1}", response, ClientDisplayString(session)));
                byte[] dataToSend = System.Text.Encoding.ASCII.GetBytes(response);
                session.Socket.BeginSend(dataToSend, 0, dataToSend.Length, SocketFlags.None, new AsyncCallback(FinalizeSending), session);
                session.Socket.BeginReceive(session.Buffer, 0, session.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCommand), session);
            }
            else
            {
                session.Socket.Close();
            }
        }

        public void FinalizeSending(IAsyncResult result)
        {
            Session session = (Session)result.AsyncState;
            session.Socket.EndSend(result);
        }

        #endregion

        /// <summary>
        /// Returns the client nicknams if the client is resgisterd, else returns client's ID. 
        /// </summary>
        /// <param name="session">Client's Session</param>
        /// <returns>Nickname or Client ID </returns>
        private static string ClientDisplayString(Session session)
        {
            if (session.User != null && session.User.Nickname != null)
                return session.User.Nickname;
            else
                return string.Format("client {0}", session.ClientID);
        }

    }
}
